package fr.mywiki.business.structure;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.ejb.FinderException;
import javax.ejb.ObjectNotFoundException;

import fr.mywiki.model.ejbAdapter.DataController;
import fr.mywiki.model.ejbAdapter.ServicesFacade;
import fr.mywiki.model.ejbAdapter.utils.PersistenceException;

/**
 * Contains several methods to deal with the structure.
 */
public class StructureManager {
	/**
	 * Maximal number of levels in each total branch. The root is level 1 and we
	 * can count until level 10 included.
	 */
	public static final int MAX_LEVELS = 10;

	/**
	 * Creates a new structure element.
	 * 
	 * @param name
	 *          the name of this structure element
	 * @param parent
	 *          the structure which is linked to the one to create. If this
	 *          parameter is <code>null</code>, the created structure is on the
	 *          higher level.
	 */
	public static StructureLite createStructure(String name, StructureLite parent)
			throws RemoteException, PersistenceException, FinderException {
		StructureLite struct = (StructureLite) DataController
				.createValue(StructureLite.TYPE_ENT);
		struct.setName(name);
		if (parent != null)
			struct.addLink(StructureLite.LINK_CHILD_TO_PARENT, parent);
		struct.update();
		return struct;
	}

	/**
	 * Returns the children of the structure element given in parameter.
	 * 
	 * @param structure
	 * @return a <code>Collection</code> of <code>StructureLite</code> objects.
	 */
	public static Collection getChildren(StructureLite structure)
			throws RemoteException, FinderException {
		return ServicesFacade.getInstance().getLinks(structure,
				StructureLite.LINK_PARENT_TO_CHILDREN);
	}
	
	/**
	 * Returns all the structures that are under the structure given in parameter.
	 * This structure itself is not included in its own descendance.
	 * 
	 * @param structure
	 *          a <code>StructureLite</code> object
	 * @return a <code>Set</code> of <code>StructureLite</code> objects.
	 */
	public static Set getDescendance(StructureLite structure) throws RemoteException, FinderException {
		Set set = new HashSet();
		
		if(hasChildren(structure)) {
			Iterator itChildren = getChildren(structure).iterator();
			while(itChildren.hasNext()) {
				StructureLite child = (StructureLite) itChildren.next();
				set.add(child);
				set.addAll(getDescendance(child));
			}
		}
		
		return set;
	}
	
	/**
	 * Returns the children of the structure element given in parameter. They are
	 * ordered by name.
	 * 
	 * @param structure
	 * @return a <code>List</code> of <code>StructureLite</code>
	 *         objects.
	 */
	public static List getOrderedChildren(StructureLite structure)
			throws RemoteException, FinderException {
		ArrayList list = new ArrayList(getChildren(structure));
		sortStructuresByName(list);
		return list;
	}
	
	/**
	 * Returns true iif the structure given in parameter has children.
	 * 
	 * @param structure a <code>StructureLite</code> object.
	 * @return a boolean
	 */
	public static boolean hasChildren(StructureLite structure)
			throws RemoteException, FinderException {
		Collection col = getChildren(structure);
		return col != null && !col.isEmpty();
	}
	
	/**
	 * Returns the parent of a structure, <code>null</code> if the structure is
	 * a root.
	 * 
	 * @param structure
	 *          a <code>Structurelite</code> object.
	 * @return a <code>StructureLite</code> object.
	 */
	public static StructureLite getParent(StructureLite structure)
			throws RemoteException, FinderException {
		Collection parents = ServicesFacade.getInstance().getLinks(structure,
				StructureLite.LINK_CHILD_TO_PARENT);
		if (parents == null || parents.isEmpty())
			return null;
		return (StructureLite) new ArrayList(parents).get(0);
	}
	
	/**
	 * Returns the level of the structure given in parameter.
	 * 
	 * @param structure
	 *          a <code>StructureLite</code> object.
	 * @return an integer between 1 and <code>MAX_LEVELS</code>.
	 */
	public static int getLevel(StructureLite structure) throws RemoteException,
			FinderException {
		StructureLite parent = getParent(structure);
		if (parent == null)
			return 1;
		return 1 + getLevel(parent);
	}
	
	/**
	 * Returns all the root structure elements, that is to say the elements which
	 * do not have a parent.
	 * 
	 * @return a Collection of <code>StructureLite</code> object
	 * TODO Optimize
	 */
	public static Collection getRootNodes() throws RemoteException, FinderException {
		HashSet set = new HashSet();
		
		Iterator itStruct = DataController.findAll(StructureLite.TYPE_ENT)
				.iterator();
		while (itStruct.hasNext()) {
			StructureLite struct = (StructureLite) itStruct.next();
			if (getParent(struct) == null)
				set.add(struct);
		}
		
		return set;
	}
	
	/**
	 * Returns all the root structure elements, that is to say the elements which
	 * do not have a parent. They are ordered by name.
	 * 
	 * @return a <code>List</code> of <code>StructureLite</code> object
	 */
	public static List getOrderedRootNodes() throws RemoteException, FinderException {
		ArrayList list = new ArrayList(getRootNodes());
		sortStructuresByName(list);
		return list;
	}
	
	/**
	 * Sorts a list of structures by their name.
	 * 
	 * @param structures a <code>List</code> of <code>StructureLite</code> objects.
	 */
	public static void sortStructuresByName(List structures) {
		Object[] array = structures.toArray();
		
		Comparator nameComp = new Comparator() {
			public int compare(Object o1, Object o2) {
				StructureLite lite1 = (StructureLite) o1;
				StructureLite lite2 = (StructureLite) o2;
				String str1 = lite1.getName();
				String str2 = lite2.getName();
				return str1.compareTo(str2);
			}
		};
		
		Arrays.sort(array,nameComp);
		structures.clear();
		structures.addAll(Arrays.asList(array));
	}
	
	/**
	 * Returns true iif the structure given in parameter is on the last authorized
	 * level of the structure
	 * 
	 * @param structure 
	 * @return true if the level is maximal
	 */
	public static boolean isOnLastLevel(StructureLite structure) throws RemoteException, FinderException {
		return getLevel(structure)==MAX_LEVELS;
	}
	
	/**
	 * Modifies the structure given in parameter.
	 * 
	 * @param structure	a <code>StructureLite</code> object.
	 * @param name a String, the new name of the structure.
	 * 
	 * @return the modified <code>StructureLite</code> object.
	 */
	public static StructureLite modifyStructure(StructureLite structure,
			String name) throws ObjectNotFoundException, RemoteException {
		structure.setName(name);

		structure.update();

		return structure;
	}
	/**
	 * Removes the structure given in parameter. Also removes the children of this element.
	 * 
	 * @param structure a <code>StructureLite</code> object.
	 */
	public static void removeStructure(StructureLite structure)
			throws RemoteException, FinderException {
		if(hasChildren(structure)) {
			Iterator itCh = getChildren(structure).iterator();
			while(itCh.hasNext())
				removeStructure((StructureLite) itCh.next());
		}
		structure.remove();
	}
	
	/**
	 * Returns the list of accounts linked to that structure.
	 * 
	 * @param structure
	 *          a <code>StructureLite</code> object.
	 * @return a <code>Collection</code> of <code>StructureLite</code>
	 *         objects.
	 */
	public static Collection getAccounts(StructureLite structure)
			throws RemoteException, FinderException {
		return ServicesFacade.getInstance().getLinks(structure,
				StructureLite.LINK_ACCOUNT_STRUCTURE);
	}
}
